package com.gitee.huanminabc.multimedia.opencv;

import com.alibaba.fastjson.JSON;
import nu.pattern.OpenCV;
import org.json.JSONArray;
import org.json.JSONObject;
import org.opencv.core.*;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.Feature2D;
import org.opencv.features2d.Features2d;
import org.opencv.features2d.ORB;
import org.opencv.imgcodecs.Imgcodecs;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.opencv.imgproc.Imgproc.COLOR_BGR2GRAY;
import static org.opencv.imgproc.Imgproc.cvtColor;

public class OpencvImageASRUtil {

    static {
        //一起加载
        OpenCV.loadShared();
    }

    //图像特征提取,ORB算法,返回提取的全部特征点
    public static String extractFeatures(String inputPath) {
        // 读取图像
        Mat src = Imgcodecs.imread(inputPath);
        // 创建一个ORB特征检测器
        Feature2D detector = ORB.create();
        // 创建一个MatOfKeyPoint对象，用于存储检测到的特征点
        MatOfKeyPoint keypoints = new MatOfKeyPoint();
        // 检测特征点
        detector.detect(src, keypoints);
        // 释放资源
        src.release();
        //将特征点转换为JSON字符串
        return keypointsToJson(keypoints);
    }

    private static String keypointsToJson(MatOfKeyPoint keypoints) {
        //将特征点转换为JSON字符串
        JSONArray jsonArray = new JSONArray();
        List<KeyPoint> keyPoints = keypoints.toList();
        for (KeyPoint keyPoint : keyPoints) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("x", keyPoint.pt.x);
            jsonObject.put("y", keyPoint.pt.y);
            jsonObject.put("size", keyPoint.size);
            jsonObject.put("angle", keyPoint.angle);
            jsonObject.put("response", keyPoint.response);
            jsonObject.put("octave", keyPoint.octave);
            jsonObject.put("class_id", keyPoint.class_id);
            jsonArray.put(jsonObject);
        }
        return jsonArray.toString();
    }

    private static MatOfKeyPoint jsonToKeyPoints(String json) {
        JSONArray keyPointsJson = new JSONArray(json);
        MatOfKeyPoint keypoints = new MatOfKeyPoint();
        List<KeyPoint> keyPoints = new ArrayList<>();
        for (int i = 0; i < keyPointsJson.length(); i++) {
            JSONObject jsonObject = keyPointsJson.getJSONObject(i);
            float x = jsonObject.getFloat("x");
            float y = jsonObject.getFloat("y");
            float size = jsonObject.getFloat("size");
            float angle = jsonObject.getFloat("angle");
            float response = jsonObject.getFloat("response");
            int octave = jsonObject.getInt("octave");
            int class_id = jsonObject.getInt("class_id");
            KeyPoint keyPoint = new KeyPoint(x, y, size, angle, response, octave, class_id);
            keyPoints.add(keyPoint);
        }
        keypoints.fromList(keyPoints);
        return keypoints;

    }


    //描述符提取
    public static String extractDescriptors(String inputPath, String features) {
        // 读取图像
        Mat src = Imgcodecs.imread(inputPath);
        // 创建一个ORB特征检测器
        Feature2D detector = ORB.create();
        // 创建一个MatOfKeyPoint对象，用于存储检测到的特征点
        MatOfKeyPoint keypoints = jsonToKeyPoints(features);
        // 创建一个Mat对象，用于存储描述符
        Mat descriptors = new Mat();
        // 计算描述符
        detector.compute(src, keypoints, descriptors);
        // 释放资源
        src.release();
        // 将描述符转换为JSON字符串
        return descriptorsToJson(descriptors);
    }

    // 开头会添加通道数 ,例如 32###[{"channel_0":[1.0,2.0,3.0],"channel_1":[4.0,5.0,6.0]}] 需要截取掉通道数
    private static String descriptorsToJson(Mat descriptors) {
        if (descriptors.empty()) {
            throw new IllegalArgumentException("The input Mat is empty.");
        }
        //获取描述符的通道数
        int channels = descriptors.channels();
        //获取通道类型
        int type = descriptors.type();

        //将描述符转换为JSON字符串
        JSONArray jsonArray = new JSONArray();
        for (int i = 0; i < descriptors.rows(); i++) {
            JSONObject jsonObject = new JSONObject();
            for (int c = 0; c < channels; c++) {
                JSONArray channelArray = new JSONArray();
                for (int j = 0; j < descriptors.cols(); j++) {
                    double value = descriptors.get(i, j)[c];
                    channelArray.put(value);
                }
                jsonObject.put("channel_" + c, channelArray);
            }
            jsonArray.put(jsonObject);
        }
        //在开头添加通道数
        Map<String, String> newJson = new HashMap<>();
        newJson.put("channels", channels + "");//通道数
        newJson.put("type", type + ""); //类型
        newJson.put("json", jsonArray.toString());
        return JSON.toJSONString(newJson);
    }

    //将JSON字符串转换为描述符
    private static Mat jsonToDescriptors(String json) {
        //截取通道数
        Map<String, String> map = JSON.parseObject(json, Map.class);
        int channels = Integer.parseInt(map.get("channels"));
        json = map.get("json");
        int type = Integer.parseInt(map.get("type"));

        //将JSON字符串转换为描述符
        JSONArray jsonArray = new JSONArray(json);
        int rows = jsonArray.length();
        // 假设所有行的长度和通道都相同，从第一行的第一个通道获取列数
        int cols = jsonArray.getJSONObject(0).getJSONArray("channel_0").length();
        // 创建一个与原始Mat类型相匹配的Mat对象（这里假设是CV_64F类型和给定的通道数）
        Mat descriptors = new Mat(rows, cols, type);
        for (int i = 0; i < rows; i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            for (int c = 0; c < channels; c++) {
                JSONArray channelArray = jsonObject.getJSONArray("channel_" + c);
                for (int j = 0; j < cols; j++) {
                    double value = channelArray.getDouble(j);
                    // 注意这里要乘以通道数加上当前的通道索引来设置Mat中的位置
                    descriptors.put(i, j * channels + c, value);
                }
            }
        }
        return descriptors;
    }

    //利用特征点和描述符进行图像匹配
    public static boolean match(String originalDescriptors, String matchDescriptors) {

        //特征转换
        Mat descriptors1 = jsonToDescriptors(originalDescriptors);
        Mat descriptors2 = jsonToDescriptors(matchDescriptors);

        //创建描述符匹配器
        DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
        //创建一个MatOfDMatch对象，用于存储匹配结果
        MatOfDMatch matches = new MatOfDMatch();
        //进行匹配
        matcher.match(descriptors1, descriptors2, matches);
        //释放资源
        descriptors1.release();
        descriptors2.release();
        //计算匹配结果
        double max_dist = 0; //最大距离
        double min_dist = 100; //最小距离
        List<DMatch> matchesList = matches.toList();
        for (DMatch match : matchesList) {
            double dist = match.distance;
            if (dist < min_dist) {
                min_dist = dist;
            }
            if (dist > max_dist) {
                max_dist = dist;
            }
        }
        //释放资源
        matches.release();
        //计算匹配结果
        return min_dist < 50; //设置一个阈值，小于50认为匹配成功,相当于一半的特征点匹配成功

    }

    //将特征点绘制到图像上
    public static void drawFeatures(String inputPath, String outputPath, String features) {
        // 读取图像
        Mat src = Imgcodecs.imread(inputPath);
        // 创建一个MatOfKeyPoint对象，用于存储检测到的特征点
        MatOfKeyPoint keypoints = jsonToKeyPoints(features);
        // 绘制特征点
        Features2d.drawKeypoints(src, keypoints, src);
        // 保存图像
        Imgcodecs.imwrite(outputPath, src);
        // 释放资源
        src.release();
    }



}
